
#include "rpi_radar_cmd.h"

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#include <linux/miscdevice.h>
#include <linux/spinlock.h>
#include <linux/fs.h>
#include <linux/poll.h>

#include <linux/ktime.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#define DEVNAME "rpi_radar"

/*
 * trig 默认为高，拉低至少10us，触发输出超声波
 * echo 输出高电平时长，表示从超声波发射到返回时间
 * 
 */

struct rpi_radar {
    struct platform_device *pdev;
    
    struct {
        int trig;
        int echo;
    } gpio;

    int irq;
    struct work_struct irq_work;
	int irq_count;
    ktime_t init_ts;
    int64_t elapse;

    int result_ready;
	wait_queue_head_t wqh;
} g_rpi_radar;

static void rpi_radar_start(void);

static void rpi_radar_trig_test(void)
{
	struct rpi_radar *radar = &g_rpi_radar;
	
	printk("rpi radar trig test\n");

    gpio_set_value(radar->gpio.trig, 0);
    udelay(100);
    gpio_set_value(radar->gpio.trig, 1);
	
	return;
}

static long rpi_radar_ioctl(struct file *fil, unsigned int cmd, unsigned long arg)
{
    if (cmd == RPI_RADAR_CMD_START)
    {
        rpi_radar_start();
    }
	else if (cmd == RPI_RADAR_CMD_TRIG_TEST)
	{
		rpi_radar_trig_test();
	}

    return 0;
}

static ssize_t rpi_radar_read(struct file *fil, char __user *buf, size_t count, loff_t *ppos)
{
    struct rpi_radar *radar = &g_rpi_radar;
    
    ssize_t res;

    res = -EAGAIN;
    if (radar->result_ready) { res = 0; }

    if (res < 0) { return res; }
	radar->result_ready = 0;

    return put_user(radar->elapse, (__u64 __user *) buf) ? -EFAULT : sizeof(radar->elapse);
}

static unsigned int rpi_radar_poll(struct file *fil, struct poll_table_struct *pt)
{
    struct rpi_radar *radar = &g_rpi_radar;

    poll_wait(fil, &radar->wqh, pt);

	if (radar->result_ready)
    {
        return POLLIN;
    }

    return 0;
}

static void rpi_radar_handler(struct work_struct *work)
{
    struct rpi_radar *radar = &g_rpi_radar;
	printk("rpi_radar, elapsed: %lldus\n", radar->elapse);

	disable_irq(radar->irq);
	radar->irq_count = 0;
	gpio_set_value(radar->gpio.trig, 0);

	spin_lock_irq(&radar->wqh.lock);
	radar->result_ready = 1;
	if (waitqueue_active(&radar->wqh))
	{
		wake_up_locked_poll(&radar->wqh, POLLIN);
	}
	spin_unlock_irq(&radar->wqh.lock);

    return;
}

static irqreturn_t rpi_radar_irq(int irq, void *dev_id)
{
	struct rpi_radar *radar = &g_rpi_radar;

    if (radar->irq_count == 1)
    {
		radar->init_ts = ktime_get();
    }
    else if (radar->irq_count == 2)
    {
        radar->elapse = ktime_to_us(ktime_sub(ktime_get(), radar->init_ts));
		schedule_work(&radar->irq_work);
	}
	radar->irq_count++;

    return IRQ_HANDLED;
}

static void rpi_radar_start(void)
{
    struct rpi_radar *radar = &g_rpi_radar;

    if (radar->irq_count > 0) { return; }

	printk("rpi radar start\n");

	enable_irq(radar->irq);
    radar->irq_count = 1;
    gpio_set_value(radar->gpio.trig, 1);
    udelay(20);
    gpio_set_value(radar->gpio.trig, 0);

    return;
}

static struct file_operations rpi_radar_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = rpi_radar_ioctl,
    .read = rpi_radar_read,
    .poll = rpi_radar_poll,
};

static struct miscdevice radar_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVNAME,
    .fops = &rpi_radar_fops,
    .mode = 0666,
};

static int rpi_radar_driver_probe(struct platform_device *pdev)
{
    struct rpi_radar *radar = &g_rpi_radar;

    struct device *dev = &pdev->dev;
    struct device_node *node = dev->of_node;

	int gpio, flags, ret = 0;
	enum of_gpio_flags of_flags;

    if (of_find_property(node, "trig-pin", NULL))
    {
        gpio = of_get_named_gpio_flags(node, "trig-pin", 0, &of_flags);
        if (gpio < 0)
        {
            dev_err(dev, "failed to get trig-pin from DT\n");
            return -1;
        }

		flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH;
		ret = devm_gpio_request_one(dev, gpio, flags, dev->driver->name);
		if (ret != 0)
        {
            dev_err(dev, "gpio_request_one('trig-pin'=%d) failed with %d\n", gpio, ret);
            return -2;
        }
        
        radar->gpio.trig = gpio;
    }
    
    if (of_find_property(node, "echo-pin", NULL))
    {
        gpio = of_get_named_gpio_flags(node, "echo-pin", 0, &of_flags);
        if (gpio < 0)
        {
            dev_err(dev, "failed to get trig-pin from DT\n");
            return -1;
        }

		flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH;
		ret = devm_gpio_request_one(dev, gpio, flags, dev->driver->name);
		if (ret != 0)
        {
            dev_err(dev, "gpio_request_one('echo-pin'=%d) failed with %d\n", gpio, ret);
            devm_gpio_free(dev, radar->gpio.trig);
            return -2;
        }

        radar->gpio.echo = gpio;
    }

	radar->pdev = pdev;
    radar->irq = gpio_to_irq(radar->gpio.echo);
    ret = devm_request_irq(dev, radar->irq, rpi_radar_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), radar->pdev->name, dev);
    if (ret != 0)
    {
        pr_err("request irq failed\n");
        return -3;
    }
    disable_irq(radar->irq);

	gpio_set_value(radar->gpio.trig, 0);
	gpio_direction_input(radar->gpio.echo);
	INIT_WORK(&radar->irq_work, rpi_radar_handler);

	radar->result_ready = 0;
	init_waitqueue_head(&radar->wqh);

	misc_register(&radar_device);
	platform_set_drvdata(pdev, radar);

	printk("rpi radar ready\n");

	return 0;
}

static int rpi_radar_driver_remove(struct platform_device *pdev)
{
    struct rpi_radar *radar = (struct rpi_radar*)platform_get_drvdata(pdev);

	spin_lock_irq(&radar->wqh.lock);
	if (waitqueue_active(&radar->wqh))
	{
		wake_up_locked_poll(&radar->wqh, POLLHUP);
	}
	spin_unlock_irq(&radar->wqh.lock);

    devm_gpio_free(&radar->pdev->dev, radar->gpio.trig);
    devm_gpio_free(&radar->pdev->dev, radar->gpio.echo);
    devm_free_irq(&radar->pdev->dev, radar->irq, &radar->pdev->dev);

    misc_deregister(&radar_device);

	return 0;
}

static const struct of_device_id dt_ids[] = {
	{ .compatible = "rpi,rpi_radar" },
	{},
};
MODULE_DEVICE_TABLE(of, dt_ids);

static struct platform_driver rpi_radar_platform_driver = {
	.driver = {
		.name   = DEVNAME,
		.owner  = THIS_MODULE,
		.of_match_table = of_match_ptr(dt_ids),
	},
	.probe  = rpi_radar_driver_probe,
	.remove = rpi_radar_driver_remove,
};

module_platform_driver(rpi_radar_platform_driver);

MODULE_LICENSE("GPL");